%%%-------------------------------------------------------------------
%%% @author liuwentao
%%% @doc
%%%
%%% @end
%%% Created : 17. 6月 2021 14:20
%%%-------------------------------------------------------------------
-module(env).
-author("liuwentao").

-behaviour(gen_server).


%% 非进程接口
-export([
     get/1
    , get/2
    , set/2
    , update_counter/2
    , save/2
    , save_counter/2
    , del/1
    , const_var/1
    , const_var/2
    , reload/0
]).

%% 进程接口
-export([
    start_link/0        %% 进程开启函数
]).


%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-define(SERVER, ?MODULE).
-include("common.hrl").

-record(state, {}).

%%%===================================================================
%%% API
%%%===================================================================
%% @doc 进程启动
-spec(start_link() ->
    {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).


%%----------------------------------------------------
%% 外部接口
%%----------------------------------------------------

%% @doc 获取变量 频繁请求
-spec const_var(any()) -> undefined | any().
const_var(Key) ->
    case erlang:get(Key) of
        undefined ->
            Val = ?MODULE:get(Key),
            put(Key, Val),
            Val;
        Val -> Val
    end.

%% @doc 获取常量 频繁请求
-spec const_var(any(), DefVal :: any()) -> undefined | any().
const_var(Key, DefVal) ->
    case const_var(Key) of
        undefined ->
            put(Key, DefVal),
            DefVal;
        Val -> Val
    end.

%% @doc 取出指定的环境变量值
%% <div>注意: 如果不存预设置，则一律返回undefined</div>
%% <div>注意: 当环境变量服务器异常时取值也会返回undefined，需要在代码中小心应对</div>
-spec get(any()) -> undefined | any().
get(Key) ->
    case catch ets:lookup(env, Key) of
        [{_, Val}] -> Val;
        {'EXIT', {badarg, _}} ->
            ?INFO("系统环境变量服务器异常：未运行或已经崩溃"),
            undefined;
        _ ->
            undefined
    end.

%% @doc 取出指定的环境变量值
%% <div>注意: 如果不存预设置，则一律返回默认值</div>
%% <div>注意: 当环境变量服务器异常时取值也会返回默认值</div>
-spec get(any(), any()) -> any().
get(Key, DefVal) ->
    case catch ets:lookup(env, Key) of
        [{_, Val}] -> Val;
        {'EXIT', {badarg, _}} ->
            ?INFO("系统环境变量服务器异常：未运行或已经崩溃"),
            DefVal;
        _ ->
            DefVal
    end.

%% @doc 设置一个环境变量值
%% <div>注意:进行并发操作get/set同一key时的事务性需要使用者来保证，或考虑使用update_counter/2</div>
%% <div>注意:设置的值并不会被保存，如果需要保存则使用save/2</div>
-spec set(any(), any()) -> any().
set(Key, Val) ->
    ets:insert(env, {Key, Val}),
    Val.

%% @doc 更新某个计数器
-spec update_counter(term(), integer()) -> integer().
update_counter(Key, Val) when is_integer(Val) ->
    ets:update_counter(env, Key, Val).

%% @doc 保存一个环境变量值
%% <div>注意:写入性能并不高(写入一次大约需要几十微秒)，所以不要用于需大量重复写入的情况</div>
-spec save(any(), any()) -> any().
save(Key, Val) ->
    ets:insert(env, {Key, Val}),
    dets:insert(env_file, {Key, Val}),
    Val.

%% @doc 更新并保存某个计数器
-spec save_counter(term(), integer()) -> integer().
save_counter(Key, Val) ->
    V = ets:update_counter(env, Key, Val),
    dets:insert(env_file, {Key, V}),
    V.

%% @doc 删除一个环境变量
-spec del(any()) -> ok | {error, term()}.
del(Key) ->
    ets:delete(env, Key),
    dets:delete(env_file, Key),
    ok.


%%----------------------------------------------------
%% 内部处理
%%----------------------------------------------------

init([]) ->
    process_flag(trap_exit, true),
    %% 环境变量存储器
    ets:new(env, [set, named_table, public, {keypos, 1}]),
    dets:open_file(env_file, [{file, "./dets/env.dets"}, {keypos, 1}, {type, set}]),
    dets:to_ets(env_file, env),
    Root = filename:absname(""),
    set(root, Root),
    case load_env_cfg(?MODULE:get(root) ++ "/env.cfg") of
        {error, Reason} ->
            ?ERR("载入配置失败: ~w", [Reason]),
            {stop, {can_not_load_env_file, Reason}};
        ok ->
            io:format("~n~n+------------------------------------------------------------+~n"),
            io:format("|                             开服啦！！！！！                |~n"),
            io:format("+----------------------------------------------------------------+~n"),
            true = erlang:set_cookie(node(), env:get(cookie)),
            ?INFO("已载入配置内容", []),
            State = #state{},
            {ok, State}
    end.

%% @doc 重载配置文件
reload() ->
    case load_env_cfg(?MODULE:get(root) ++ "/env.cfg") of
        {error, Reason} ->
            ?ERR("载入配置env失败: ~w", [Reason]),
            {stop, {can_not_load_env_file, Reason}};
        ok ->
            ok
    end.

handle_call(_Request, _From, State) ->
    {noreply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    dets:close(env_file),
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%----------------------------------------------------
%% 私有函数
%%----------------------------------------------------

%% @doc 从指定的文件中加载变量设置
-spec load_env_cfg(string()) -> ok | {error, term()}.
load_env_cfg(File) ->
    case util:load(File) of
        {error, Reason} -> {error, Reason};
        {ok, undefined} -> ok;
        {ok, L} ->
            [set(K, V) || {K, V} <- L],
            ok
    end.

%%%===================================================================
%%% 工具函数
%%%===================================================================